package fs

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/goccy/go-json"

	"github.com/klauspost/compress/zstd"
	"golang.org/x/sync/errgroup"
)

// Metadata 定义文件的元数据结构
type Metadata struct {
	Name   string    `json:"name"`
	Size   int64     `json:"size"`
	Type   string    `json:"type"`
	Time   time.Time `json:"time"`
	Chunks []string  `json:"chunks"`
}

var pathPrefix string

func InitFs(p string) {
	pathPrefix = p
}

func PathFromHash(hash string) string {
	return filepath.Join(pathPrefix, string(hash[0]), hash[1:3], hash[3:])
}

func SaveFile(hashCode string, data []byte) error {
	// 构建文件路径
	filePath := PathFromHash(hashCode)

	// 创建目录
	err := os.MkdirAll(filepath.Dir(filePath), 0o755)
	if err != nil {
		return err
	}

	// 写入文件
	err = os.WriteFile(filePath, data, 0o644)
	if err != nil {
		return err
	}

	return nil
}

func getSHA256(data []byte) []byte {
	// 创建 SHA256 哈希对象
	hash := sha256.New()

	// 将数据写入哈希对象
	hash.Write(data)

	// 计算哈希值
	sha256Hash := hash.Sum(nil)

	return sha256Hash
}

func getSHA256String(hash []byte) string {
	hashString := hex.EncodeToString(hash)
	return strings.ToUpper(hashString[:15])
}

func Sum15BitSHA256(data []byte) string {
	return getSHA256String(getSHA256(data))
}

// CompressChunk 使用zstd压缩分片
func CompressChunk(chunk []byte) ([]byte, error) {
	var b bytes.Buffer
	w, err := zstd.NewWriter(&b)
	if err != nil {
		return nil, err
	}

	_, err = w.Write(chunk)
	if err != nil {
		return nil, err
	}

	err = w.Close()
	if err != nil {
		return nil, err
	}

	return b.Bytes(), nil
}

func DecompressChunk(chunkPath string) ([]byte, error) {
	chunkFile, err := os.ReadFile(chunkPath)
	if err != nil {
		return nil, err
	}
	d, err := zstd.NewReader(nil)
	if err != nil {
		return nil, err
	}
	defer d.Close()
	res, err := d.DecodeAll(chunkFile, nil)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// SaveMetadata 保存元数据
func SaveMetadata(metaFilePath string, metadata *Metadata) error {
	metaBytes, err := json.Marshal(metadata)
	if err != nil {
		return err
	}
	err = os.MkdirAll(filepath.Dir(metaFilePath), 0o755)
	if err != nil {
		return err
	}
	err = os.WriteFile(metaFilePath, metaBytes, 0o644)
	if err != nil {
		return err
	}
	return nil
}

// LoadMetadata 加载元数据
func LoadMetadata(metaFilePath string) (*Metadata, error) {
	b, err := os.ReadFile(metaFilePath)
	if err != nil {
		return nil, err
	}

	var metadata Metadata
	err = json.Unmarshal(b, &metadata)
	if err != nil {
		return nil, err
	}

	return &metadata, nil
}

func MultiDecompressedReader(filePaths []string) (io.Reader, error) {
	readers := make([]io.Reader, len(filePaths))
	for i, filePath := range filePaths {
		file, err := os.Open(filePath)
		if err != nil {
			return nil, err
		}

		zr, err := zstd.NewReader(file)
		if err != nil {
			file.Close()
			return nil, err
		}

		readers[i] = zr
	}

	return io.MultiReader(readers...), nil
}

func IsPathExist(hash string) bool {
	path := PathFromHash(hash)
	_, err := os.Stat(path)
	if err == nil {
		return true // 路径存在
	}

	if os.IsNotExist(err) {
		return false // 路径不存在
	}

	return false // 获取路径信息出错
}

// SplitFile 将文件分割成chunkSize大小的分片
func SplitFile(reader io.Reader, chunkSize int) ([]string, error) {
	var chunks []string
	var g errgroup.Group
	for {
		buffer := make([]byte, chunkSize)
		read, err := reader.Read(buffer)
		if err != nil && err != io.EOF {
			return nil, err
		}

		if read > 0 {
			chunk := buffer[:read]
			hashCode := Sum15BitSHA256(chunk)
			chunks = append(chunks, hashCode)
			if !IsPathExist(hashCode) {
				g.Go(func() error {
					compressedChunk, err := CompressChunk(chunk)
					if err != nil {
						return err
					}
					err = SaveFile(hashCode, compressedChunk)
					if err != nil {
						return err
					}
					return nil
				})
			}
		}

		if err == io.EOF {
			break
		}
	}
	err := g.Wait()
	if err != nil {
		return nil, err
	}
	return chunks, nil
}
